<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇游戏</title>
    <style>
        /* 清除默认样式 */
        * {
            margin: 0;
            padding: 0;
            /* 盒子的计算方式 */
            box-sizing: border-box;
        }

        /* 游戏外部容器 */
        #main {
            width: 360px;
            height: 420px;
            background-color: #b7d4a8;
            border: 10px solid #000;
            margin: 50px auto;
            border-radius: 20px;
        }

        /* 舞台 */
        #stage {
            width: 304px;
            height: 304px;
            border: 2px solid #000;
            margin: 20px auto;
            position: relative;
        }

        /* 蛇的本体*/
        #snake>div {
            width: 10px;
            height: 10px;
            background-color: #000;
            position: absolute;
            border: 1px solid #b7d4a8;
            /* left: 0; */
        }

        /* 猎物外部盒子 */
        #food {
            width: 10px;
            height: 10px;
            position: absolute;
            top: 100px;
            left: 120px;
            display: flex;
            flex-flow: wrap;
        }

        /* 猎物的内部 */
        #food>div {
            width: 5px;
            height: 5px;
            background-color: #000;
            transform: rotate(45deg);
        }

        /* 等级分数显示 */
        #info {
            width: 304px;
            margin: 0 auto;
            display: flex;
            justify-content: space-between;
            font: bold 20px courier;
        }
    </style>
</head>

<body>
    <!-- 游戏外部容器 -->
    <div id="main">
        <!-- 贪吃蛇的舞台 -->
        <div id="stage">
            <!-- 猎物 -->
            <div id="food">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
            <!-- 蛇的整体 -->
            <div id="snake">
                <!-- 第一个div标识蛇头 -->
                <div></div>
            </div>
        </div>

        <!-- 分数和等级显示 -->
        <div id="info">
            <!-- 分数 -->
            <div>SCORE:<span id="score">0</span></div>
            <!-- 等级 -->
            <div>LEVEL:<span id="level">1</span></div>

        </div>
    </div>

    <script>
        // 获取蛇的容器
        const snake = document.getElementById('snake');
        // 获取蛇的各个部分
        const snakes = snake.getElementsByTagName('div');
        // 获取分数和等级的span
        const scoreSpan = document.getElementById('score');
        const levelSpan = document.getElementById('level');
        // 分数变量
        let score = 0;
        // 等级变量
        let level = 0;


        // 声明一个变量用来存储蛇移动的方向
        let dir

        // 获取猎物
        const food = document.getElementById('food');
        // 猎物坐标
        function changeFood() {
            /*
                猎物随机生成，生成坐标为舞台的宽高大小，0-300，但是猎物本身有5个像素，加上蛇的移动速度为10像素，所以坐标必须为0-290为10的倍数        
                random() 方法是生成0到1之间的随机数，小数也包括, 乘以29表示生成0-29之间的随机数但是不会包括0也不会包括29
            */
            // 生成0-29之间的随机数
            const x = Math.floor(Math.random() * 30) * 10;
            const y = Math.floor(Math.random() * 30) * 10;

            // 设置猎物的坐标
            food.style.left = x + 'px';
            food.style.top = y + 'px';
            // console.log(x, y);
        }
        // 调用函数，随即生成猎物
        changeFood();

        /* 创建一个数组用来存储方向键，并判断除了这个数组里面的按键，点击任意按键都不会有任何效果，用来解决点击
        任意按键停止游戏情况 */
        let keyArr = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];

        /* 
            解决快速按两次按键的bug
                情况：假如原始方向是向下的，按左的瞬间点上，蛇就会掉头
                原因就是300毫秒之内点击了两次按键
                
        */
        // 创建一个变量记录按键的状态
        let keyActive = true; // true表示按键可用


        // 创建一个对象，用来存储dir的值
        const reObj = {
            ArrowUp: 'ArrowDown',
            ArrowDown: 'ArrowUp',
            ArrowLeft: 'ArrowRight',
            ArrowRight: 'ArrowLeft'
        }


        // 绑定键盘事件keydown keyup
        // 键盘事件只能绑定给获取焦点的元素或是document
        document.addEventListener('keydown', (event) => {
            /*           
                当我们按着某个按键不松开时，按键按下事件会持续触发
                但是第一次和第二次触发的间隔会比较长
                这样导致练习中蛇不能流畅的移动
                贪食蛇的游戏，蛇是不能停止运动的，（解决蛇的移动流畅问题）所以我们要把方向和速度分开控制，反向用变量存储，速度用定时器来调整
             */

            // 判断按的按键在不在这个数组里面,不在就点击不生效
            if (keyActive && keyArr.includes(event.key)) {// includes方法用来判断数组中有没有指定的值，返回布尔值
                // 在数组里就赋值方向
                /* 
                    游戏禁止掉头
                        构成条件：
                            1.身体超过2
                            2.不能和原始方向相反
                        处理：
                            保持原来的方向不变（不修改dir的值）
                            这段代码的reObj[dir] != event.key 判断逻辑：
                            这里的判断逻辑就是，假如蛇原来的方向是向上的那么他的e.key就是ArrowUp，获取reObj属性的值就是ArrowDown，然后判断点击的值是否是ArrowDown 是的话就是掉头不赋值dir
               */
                // 判断是否掉头，不掉头就进入判断
                if (snakes.length < 2 || reObj[dir] != event.key) {
                    dir = event.key;
                    keyActive = false;
                }

            }
        });

        // 使用定时器来控制蛇的速度
        setTimeout(function move() {
            // 获取蛇头
            const head = snakes[0];
            // 获取蛇头的坐标
            let x = head.offsetLeft;
            let y = head.offsetTop;

            // 判断用户按下的按键
            switch (dir) {
                case 'ArrowUp':
                    // console.log('上');
                    // 每隔多少秒在原来值得基础上减10
                    y = y - 10;
                    break;
                case 'ArrowDown':
                    // console.log('下');
                    y += 10;
                    break;
                case 'ArrowLeft':
                    // console.log('左');
                    x -= 10;
                    break;
                case 'ArrowRight':
                    // console.log('右');
                    x += 10;
                    break;
            }
            /*
                碰撞检测
                    -检查蛇头有没有碰到猎物，其实就是检查蛇头的坐标和猎物的坐标相不相等
            */
            // 判断蛇头是否吃到猎物
            if (head.offsetTop === food.offsetTop && head.offsetLeft === food.offsetLeft) {
                // 1.进入判断说明吃到猎物，并调用随机生成函数改变猎物的位置
                changeFood();
                // 2.增加蛇的身体
                snake.insertAdjacentHTML('beforeend', '<div/>')
                score++
                scoreSpan.textContent = score

                // 检查等级
                if (score % 10 === 0 && level < 14) {
                    level++
                    levelSpan.textContent = level + 1;
                }
            }

            /* 
                判断游戏是否结束：
                    1.撞墙
                    2.撞自己
            */
            // 判断是否撞墙
            if (x < 0 || x > 290 || y < 0 || y > 290) {
                alert('撞墙了，游戏结束！');
                return
            }
            // 判断是否撞自己
            // 简单来说就是蛇移动的坐标是否等于身体各个部分的坐标，减一的判断是为了头尾相连不撞自己，因为头移动尾巴也会跟着移动
            for (let i = 0; i < snakes.length - 1; i++) {
                if (snakes[i].offsetLeft === x && snakes[i].offsetTop === y) {
                    alert('撞到自己了，游戏结束！');
                    return
                }
            }

            /* 
                蛇的身体跟着蛇头一起动的逻辑
                    - 1.要使得身体跟蛇头一起移动，我们就要将组成蛇最后面的那个div，变化成蛇头
                    - 2.第二个方法就是将每个身体的div继承上个身体div的坐标位置
            */
            // 获取尾巴 身体得各个部分是一个数组，获取数组里最后一个元素
            const tail = snakes[snakes.length - 1];
            // 移动蛇的位置,将蛇尾移动到蛇头的前方,这里我们只是把蛇尾样式改变到前面并没有把，
            // 蛇身体各个部分的身体结构的数组里面的尾部div变换到最前面，如果不变数组的结构就会导致蛇停止移动
            tail.style.left = x + 'px';
            tail.style.top = y + 'px';
            // 改变尾部在身体数组里的结构
            snake.insertAdjacentElement('afterbegin', tail);

            keyActive = true;

            // 判断完成之后，再次调用，让蛇无限循环的走下去
            // 减level让游戏增加难度,level为15的话就没有速度可言了，所以在上面得判断不能大于15
            setTimeout(move, 300 - level * 20)
        }, 300);




    </script>
</body>

</html>